#include "processPool.h"
#include "command.h"
#include "tools.h"

void processPool_tInit(processPool_t* pPool, struct epoll_event** pevtList)
{
    //子进程的信息记录数组i, 堆上申请的空间, processChild是一个数组存储
    pPool->proChild = (processChild* )calloc(pPool->processNum, sizeof(processChild));
    //pevtList数组首地址，在epoll_wait返回时，存储已经就绪文件描述符信息
    //就绪链表
    *pevtList = (struct epoll_event* )
        calloc(MAXCONNS, sizeof(struct epoll_event));


}

void processPool_tDestroy(processPool_t* pPool, struct epoll_event* pevtList)
{
    //关闭管道的pipefd文件描述符
    for (int i = 0; i < pPool->processNum; i++) {
        close(pPool->proChild[i].pipefd); 
    }
    free(pPool->proChild);
    free(pevtList);
}

void makeChild(processPool_t* pPool)
{
    for (int i = 0; i < pPool->processNum; i++) {
        //父子进程间使用socketpair这个全双工的管道
        int pipefd[2];  //
        //两个文件描述符
        //socketpair全双工的
        socketpair(AF_LOCAL, SOCK_STREAM, 0, pipefd);

        pid_t pid = fork();
        if (0 == pid) {
            //子进程要做的是任务
            close(pipefd[0]); //对父子进程间管道，子进程做发送操作
            childHandleTask(pipefd[1]);
            exit(0);
        }
        //父进程记录子进程的信息，这里没有waitpid()?
        close(pipefd[1]);
        pPool->proChild[i].pid = pid;
        pPool->proChild[i].pipefd = pipefd[0];  //父进程从这边读
        printf("father pipefd[0] = %d\n", pipefd[0]);
    }
}

//printf()加\n防止缓冲区未刷新
void childHandleTask(int pipefd)
{
    int retVal = -1;
    //栈上申请空间,也可以传地址给别的函数使用!----注意
    char bufr[BUFFERSIZE] = {0};
    char bufse[BUFFERSIZE] = {0};
    //堆申请空间要释放，
    //堆得空间才能传给别的函数使用----堆上申请的空间在recv函数使用时会只收到8个字>
    // char* bufr = (char* )calloc(BUFFERSIZE, sizeof(char));
    //char* bufse = (char* )calloc(BUFFERSIZE, sizeof(char));
    printf("child process %d is running.\n", getpid());
    while (1) {
        //printf("child write fd = %d\n", pipefd);

        //recvFd阻塞式函数,子进程阻塞在这里，等待分配任务！！！
        //1、子线程从父进程获得关联客户端的文件描述符peerfd
        int peerfd = recvFd(pipefd);
        printf("peerfd in child: %d\n", peerfd);

        //用户登录验证
        authentication(peerfd);

        //可能需要循环等待client1过来的消息，直到client关闭
        while (1) {
            //2、接收客户端发过来的消息命令
            memset(bufr, 0, BUFFERSIZE);
            //
            //使用小火车结束数据---因为TCP字节流，无法确定过来的消息边界在哪里
            //使用小火车私有协议确定边界---最重要
            int len;
            retVal = recv(peerfd, &len, 4, 0);  //int len来接收的文件名长度
            ERROR_CHECK(retVal, -1, "recv in child 1");
            retVal = recv(peerfd, bufr, len, 0);
            printf("recv retVal = %d\n", retVal);
            ERROR_CHECK(retVal, -1, "recv in child 2");

            //2.1 client1发过来连接断开消息，退出循环
            if (0 == retVal) {
                printf("connect fd %d has closed.\n", peerfd);
                close(peerfd);
                break;
            }//其他retVal返回值得处理
            
            //记录日志
            clientlog(bufr);


            //3、解析客户端发过来的命令
            printf("command from client: %s\n", bufr);

            memset(bufse, 0, BUFFERSIZE);
            retVal = do_command(bufr, bufse, peerfd);  //bufse传入传出参数

            if (-1 == retVal) {
                puts("do_command error");
            }
            //5、返回给客户端命令执行结果，消息bufse
            //若buf空，管道另一端父进程接收到空，
            // printf("send client: %s\n", bufse);
            int retVal = send(peerfd, bufse, strlen(bufse), 0);
            ERROR_CHECK(retVal, -1, "send in child");
        }

        //3、通知父进程，任务已执行结束
        int one = 1;
        //pipfd子进程与父进程的管道通信
        int length = write(pipefd, &one, sizeof(one)); 
        printf("hi, father, length = %d\n", length);
    }
    //释放堆空间
    //free(bufr);
    //free(bufse);
}

//事件发生
void eventsHappen(int nready, struct epoll_event* pevtList, int listenfd, processPool_t* pPool)
{
    for (int i = 0; i < nready; i++) {
        int fd = pevtList[i].data.fd;  //就绪的事件
        if (fd == listenfd) {
            //1、有客户端到来、有客户端到来
            int peerfd = accept(listenfd, NULL, NULL);
            printf("peerfd in event: %d\n", peerfd);

            //记录日志
            char msg[BUFFERSIZE] = {0};
            sprintf(msg, "client connect! peerfd: %d", peerfd);
            clientlog(msg);

            //查找一个空闲的子进程，发送peerfd
            for (int j = 0; j < pPool->processNum; j++) {
                if (!pPool->proChild[j].busy) {
                    sendFd(pPool->proChild[j].pipefd, peerfd); //给子进程发送一次，子进程执行一次任务
                    pPool->proChild[j].busy = 1; //更改子进程状态
                    break;
                }
            }
        }
        else if (pevtList[i].events & EPOLLIN){
            //监听的管道事件发生
            int howmany;
            //子进程任务结束，发送过来的通知one
            int length = read(fd, &howmany, sizeof(howmany));
            ERROR_CHECK(length, -1, "read");
            if (0 == length) {
                continue;  //break退出了for循环！！！！--注意
                //使用continue，即不执行下面的语句了，跳转到循环体的末尾！
            }

            //经常刷屏刷在这里
            printf(">> parent pipe accept length = %d\n", length);

            //将执行完任务的子进程标记为空闲
            //进程池的大小事先确定的
            for (int i = 0; i < pPool->processNum; i++) {
                if (pPool->proChild[i].pipefd == fd) {
                    pPool->proChild[i].busy = 0;
                    printf("child %d is leisure.\n", pPool->proChild[i].pid);
                    break;
                }
            }
        }
        else {
            puts("error!");
            exit(1);
        }
    }
}


//把fd通过管道，让子进程关联到父进程的fd文件描述符
int sendFd(int pipefd, int fd)//成功返回0
{
    //1、准备一个iovec结构体
    struct iovec iov; //栈上
    memset(&iov, 0, sizeof(iov));
    char buf[10] = {0};
    strcpy(buf, "hello,");
    iov.iov_base = buf;
    iov.iov_len = strlen(buf);

    //2、准备一个cmsghdr结构体
    size_t len = CMSG_LEN(sizeof(fd));
    struct cmsghdr* cmsg = (struct cmsghdr* )calloc(1, len);
    cmsg->cmsg_len = len;
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS; //附属数据对象时文件描述符
    int* pfd = (int* )CMSG_DATA(cmsg);
    *pfd = fd;  //实际额外附属数据所在位置

    //3、组建一个msghdr结构体
    struct msghdr msg;  //栈上
    memset(&msg, 0, sizeof(msg));
    msg.msg_iov = &iov;  //这个必填
    msg.msg_iovlen = 1;
    msg.msg_control = cmsg;
    msg.msg_controllen = len;

    //4、进行发送操作
    int retVal = sendmsg(pipefd, &msg, 0);
    ERROR_CHECK(retVal, -1, "retVal");

    return 0;
}
int recvFd(int pipefd)//返回文件描述符，关联父进程中的文件对象
{
    //1、准备一个iovec结构体
    struct iovec iov; //栈上
    memset(&iov, 0, sizeof(iov));
    char buf[10] = {0};
    iov.iov_base = buf;
    iov.iov_len = sizeof(buf);

    //2、准备一个cmsghdr结构体
    size_t len = CMSG_LEN(sizeof(int));
    struct cmsghdr* cmsg = (struct cmsghdr* )calloc(1, len);
    cmsg->cmsg_len = len;
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS; //附属数据对象时文件描述符

    //3、组建一个msghdr结构体
    struct msghdr msg;  //栈上
    memset(&msg, 0, sizeof(msg));
    msg.msg_iov = &iov;  //这个必填
    msg.msg_iovlen = 1;
    msg.msg_control = cmsg;
    msg.msg_controllen = len;

    //4、进行接收工作
    int retVal = recvmsg(pipefd, &msg, 0);
    ERROR_CHECK(retVal, -1, "retVal");
    int fd = *(int* )CMSG_DATA(cmsg);
    printf("iov[0]: %s\n", (char* )msg.msg_iov[0].iov_base);

    return fd;
}

